DO NOT MERGE: Parse Android .map files for unknown filesystem. When generating a filesystem during the Android build, we also generate a text file with .map extension with the list of files and the blocks in the filesystem they are located. For filesystems unsupported in delta_generator (like squashfs) we use this text file to produce efficient delta payloads. Bug: 28150981 Test: Added unittest for parsing. Generated a delta payload of a squashfs image. (cherry-pick from 20bdc70e540e021c4a26553dfdbfd2fabe8ea432) Change-Id: I5b83241622903259d5d40ae73f3f908756a77afd 
diff --git a/Android.mk b/Android.mk index 3f44ecc..a698bbe 100644 --- a/Android.mk +++ b/Android.mk 
@@ -655,6 +655,7 @@  payload_generator/graph_types.cc \  payload_generator/graph_utils.cc \  payload_generator/inplace_generator.cc \ + payload_generator/mapfile_filesystem.cc \  payload_generator/payload_file.cc \  payload_generator/payload_generation_config.cc \  payload_generator/payload_signer.cc \ @@ -944,6 +945,7 @@  payload_generator/full_update_generator_unittest.cc \  payload_generator/graph_utils_unittest.cc \  payload_generator/inplace_generator_unittest.cc \ + payload_generator/mapfile_filesystem_unittest.cc \  payload_generator/payload_file_unittest.cc \  payload_generator/payload_generation_config_unittest.cc \  payload_generator/payload_signer_unittest.cc \ 
diff --git a/payload_generator/delta_diff_generator.cc b/payload_generator/delta_diff_generator.cc index 3295df0..1db2144 100644 --- a/payload_generator/delta_diff_generator.cc +++ b/payload_generator/delta_diff_generator.cc 
@@ -92,10 +92,7 @@    // Select payload generation strategy based on the config.  unique_ptr<OperationsGenerator> strategy; - // We don't efficiently support deltas on squashfs. For now, we will - // produce full operations in that case. - if (!old_part.path.empty() && - !utils::IsSquashfsFilesystem(new_part.path)) { + if (!old_part.path.empty()) {  // Delta update.  if (config.version.minor == kInPlaceMinorPayloadVersion) {  LOG(INFO) << "Using generator InplaceGenerator()."; 
diff --git a/payload_generator/generate_delta_main.cc b/payload_generator/generate_delta_main.cc index 99af679..0716c1f 100644 --- a/payload_generator/generate_delta_main.cc +++ b/payload_generator/generate_delta_main.cc 
@@ -20,7 +20,6 @@  #include <sys/types.h>  #include <unistd.h>   -#include <set>  #include <string>  #include <vector>   @@ -46,7 +45,6 @@  // and an output file as arguments and the path to an output file and  // generates a delta that can be sent to Chrome OS clients.   -using std::set;  using std::string;  using std::vector;   @@ -259,6 +257,17 @@  "a single argument with a colon between paths, e.g. "  "/path/to/part:/path/to/part2:/path/to/last_part . Path has "  "to match the order of partition_names."); + DEFINE_string(old_mapfiles, + "", + "Path to the .map files associated with the partition files " + "in the old partition. The .map file is normally generated " + "when creating the image in Android builds. Only recommended " + "for unsupported filesystem. Pass multiple files separated by " + "a colon as with -old_partitions."); + DEFINE_string(new_mapfiles, + "", + "Path to the .map files associated with the partition files " + "in the new partition, similar to the -old_mapfiles flag.");  DEFINE_string(partition_names,  string(kLegacyPartitionNameRoot) + ":" +  kLegacyPartitionNameKernel, @@ -404,6 +413,16 @@  // PayloadGenerationConfig.  PayloadGenerationConfig payload_config;  vector<string> partition_names, old_partitions, new_partitions; + vector<string> old_mapfiles, new_mapfiles; + + if (!FLAGS_old_mapfiles.empty()) { + old_mapfiles = base::SplitString( + FLAGS_old_mapfiles, ":", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + } + if (!FLAGS_new_mapfiles.empty()) { + new_mapfiles = base::SplitString( + FLAGS_new_mapfiles, ":", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); + }    partition_names =  base::SplitString(FLAGS_partition_names, ":", base::TRIM_WHITESPACE, @@ -448,6 +467,8 @@  << "Partition name can't be empty, see --partition_names.";  payload_config.target.partitions.emplace_back(partition_names[i]);  payload_config.target.partitions.back().path = new_partitions[i]; + if (i < new_mapfiles.size()) + payload_config.target.partitions.back().mapfile_path = new_mapfiles[i];  }    if (payload_config.is_delta) { @@ -464,6 +485,8 @@  for (size_t i = 0; i < partition_names.size(); i++) {  payload_config.source.partitions.emplace_back(partition_names[i]);  payload_config.source.partitions.back().path = old_partitions[i]; + if (i < old_mapfiles.size()) + payload_config.source.partitions.back().mapfile_path = old_mapfiles[i];  }  }   
diff --git a/payload_generator/mapfile_filesystem.cc b/payload_generator/mapfile_filesystem.cc new file mode 100644 index 0000000..f4f0804 --- /dev/null +++ b/payload_generator/mapfile_filesystem.cc 
@@ -0,0 +1,151 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "update_engine/payload_generator/mapfile_filesystem.h" + +#include <algorithm> +#include <map> + +#include <base/files/file_util.h> +#include <base/logging.h> +#include <base/strings/string_number_conversions.h> +#include <base/strings/string_split.h> +#include <brillo/make_unique_ptr.h> + +#include "update_engine/common/utils.h" +#include "update_engine/payload_generator/extent_ranges.h" +#include "update_engine/payload_generator/extent_utils.h" +#include "update_engine/update_metadata.pb.h" + +using std::string; +using std::vector; + +namespace { +// The .map file is defined in terms of 4K blocks. +size_t kMapfileBlockSize = 4096; +} // namespace + +namespace chromeos_update_engine { + +std::unique_ptr<MapfileFilesystem> MapfileFilesystem::CreateFromFile( + const string& filename, const string& mapfile_filename) { + if (filename.empty() || mapfile_filename.empty()) + return nullptr; + + off_t file_size = utils::FileSize(filename); + if (file_size < 0) + return nullptr; + + if (file_size % kMapfileBlockSize) { + LOG(ERROR) << "Image file " << filename << " has a size of " << file_size + << " which is not multiple of " << kMapfileBlockSize; + return nullptr; + } + off_t num_blocks = file_size / kMapfileBlockSize; + + if (!utils::FileExists(mapfile_filename.c_str())) { + LOG(ERROR) << "File " << mapfile_filename << " doesn't exist"; + return nullptr; + } + + return brillo::make_unique_ptr( + new MapfileFilesystem(mapfile_filename, num_blocks)); +} + +MapfileFilesystem::MapfileFilesystem(const string& mapfile_filename, + off_t num_blocks) + : mapfile_filename_(mapfile_filename), num_blocks_(num_blocks) {} + +size_t MapfileFilesystem::GetBlockSize() const { + return kMapfileBlockSize; +} + +size_t MapfileFilesystem::GetBlockCount() const { + return num_blocks_; +} + +bool MapfileFilesystem::GetFiles(vector<File>* files) const { + files->clear(); + + string file_data; + if (!base::ReadFileToString(base::FilePath(mapfile_filename_), &file_data)) { + LOG(ERROR) << "Unable to read .map file: " << mapfile_filename_; + return false; + } + + // Iterate over all the lines in the file and generate one File entry per + // line. + vector<base::StringPiece> lines = base::SplitStringPiece( + file_data, "\n", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL); + for (const base::StringPiece& line : lines) { + File mapped_file; + + mapped_file.extents = {}; + size_t delim, last_delim = line.size(); + while ((delim = line.rfind(' ', last_delim - 1)) != string::npos) { + string blocks = + line.substr(delim + 1, last_delim - (delim + 1)).as_string(); + size_t dash = blocks.find('-', 0); + uint64_t block_start, block_end; + if (dash == string::npos && base::StringToUint64(blocks, &block_start)) { + mapped_file.extents.push_back(ExtentForRange(block_start, 1)); + } else if (dash != string::npos && + base::StringToUint64(blocks.substr(0, dash), &block_start) && + base::StringToUint64(blocks.substr(dash + 1), &block_end)) { + if (block_end < block_start) { + LOG(ERROR) << "End block " << block_end + << " is smaller than start block " << block_start + << std::endl + << line; + return false; + } + if (block_end > static_cast<uint64_t>(num_blocks_)) { + LOG(ERROR) << "The end block " << block_end + << " is past the end of the file of " << num_blocks_ + << " blocks" << std::endl + << line; + return false; + } + mapped_file.extents.push_back( + ExtentForRange(block_start, block_end - block_start + 1)); + } else { + // If we can't parse N or N-M, we assume the block is actually part of + // the name of the file. + break; + } + last_delim = delim; + } + // We parsed the blocks from the end of the line, so we need to reverse + // the Extents in the file. + std::reverse(mapped_file.extents.begin(), mapped_file.extents.end()); + + if (last_delim == string::npos) + continue; + mapped_file.name = line.substr(0, last_delim).as_string(); + + files->push_back(mapped_file); + } + + return true; +} + +bool MapfileFilesystem::LoadSettings(brillo::KeyValueStore* store) const { + // Settings not supported in mapfile since the storage format is unknown. + LOG(ERROR) << "mapfile doesn't support LoadSettings()."; + return false; +} + +} // namespace chromeos_update_engine 
diff --git a/payload_generator/mapfile_filesystem.h b/payload_generator/mapfile_filesystem.h new file mode 100644 index 0000000..fc03c4c --- /dev/null +++ b/payload_generator/mapfile_filesystem.h 
@@ -0,0 +1,65 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +// A filesystem parser based on the Android .map files. When generating a +// filesystem with the Android tools, either squashfs or ext4, a .map file can +// be generated at the same time with the list of files and the 4K-blocks where +// the data for those files is located in the filesystem. This class parses this +// .map text file instead of parsing the structure of the actual filesystem +// contents. + +#ifndef UPDATE_ENGINE_PAYLOAD_GENERATOR_MAPFILE_FILESYSTEM_H_ +#define UPDATE_ENGINE_PAYLOAD_GENERATOR_MAPFILE_FILESYSTEM_H_ + +#include "update_engine/payload_generator/filesystem_interface.h" + +#include <memory> +#include <string> +#include <vector> + +namespace chromeos_update_engine { + +class MapfileFilesystem : public FilesystemInterface { + public: + static std::unique_ptr<MapfileFilesystem> CreateFromFile( + const std::string& filename, const std::string& mapfile_filename); + virtual ~MapfileFilesystem() = default; + + // FilesystemInterface overrides. + size_t GetBlockSize() const override; + size_t GetBlockCount() const override; + + // All the generated FilesystemInterface::File are reported as regular files. + // Files may overlap with other files in the same block. + bool GetFiles(std::vector<File>* files) const override; + + bool LoadSettings(brillo::KeyValueStore* store) const override; + + private: + MapfileFilesystem(const std::string& mapfile_filename, off_t num_blocks); + + // The file where the map filesystem is stored. + std::string mapfile_filename_; + + // The number of blocks in the filesystem. + off_t num_blocks_; + + DISALLOW_COPY_AND_ASSIGN(MapfileFilesystem); +}; + +} // namespace chromeos_update_engine + +#endif // UPDATE_ENGINE_PAYLOAD_GENERATOR_MAPFILE_FILESYSTEM_H_ 
diff --git a/payload_generator/mapfile_filesystem_unittest.cc b/payload_generator/mapfile_filesystem_unittest.cc new file mode 100644 index 0000000..36ae3bf --- /dev/null +++ b/payload_generator/mapfile_filesystem_unittest.cc 
@@ -0,0 +1,134 @@ +// +// Copyright (C) 2016 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "update_engine/payload_generator/mapfile_filesystem.h" + +#include <unistd.h> + +#include <map> +#include <string> +#include <vector> + +#include <base/format_macros.h> +#include <base/logging.h> +#include <base/strings/string_number_conversions.h> +#include <base/strings/string_util.h> +#include <base/strings/stringprintf.h> +#include <gtest/gtest.h> + +#include "update_engine/common/test_utils.h" +#include "update_engine/common/utils.h" +#include "update_engine/payload_generator/extent_ranges.h" +#include "update_engine/payload_generator/extent_utils.h" + +using std::map; +using std::string; +using std::unique_ptr; +using std::vector; + +namespace chromeos_update_engine { + +namespace { + +// Checks that all the blocks in |extents| are in the range [0, total_blocks). +void ExpectBlocksInRange(const vector<Extent>& extents, uint64_t total_blocks) { + for (const Extent& extent : extents) { + EXPECT_LE(0U, extent.start_block()); + EXPECT_LE(extent.start_block() + extent.num_blocks(), total_blocks); + } +} + +} // namespace + +class MapfileFilesystemTest : public ::testing::Test { + protected: + test_utils::ScopedTempFile temp_file_{"mapfile_file.XXXXXX"}; + test_utils::ScopedTempFile temp_mapfile_{"mapfile_mapfile.XXXXXX"}; +}; + +TEST_F(MapfileFilesystemTest, EmptyFilesystem) { + unique_ptr<MapfileFilesystem> fs = MapfileFilesystem::CreateFromFile( + temp_file_.path(), temp_mapfile_.path()); + ASSERT_NE(nullptr, fs.get()); + + EXPECT_EQ(0U, fs->GetBlockCount()); + // .map files are always 4KiB blocks. + EXPECT_EQ(4096U, fs->GetBlockSize()); +} + +TEST_F(MapfileFilesystemTest, SeveralFileFormatTest) { + string text = + "/fileA 1\n" + "/fileB 2-4\n" + "/fileC 5-6 9 11-12\n" + "/file with spaces 14 19\n" + "/1234 7\n"; + test_utils::WriteFileString(temp_mapfile_.path(), text); + EXPECT_EQ(0, HANDLE_EINTR(truncate(temp_file_.path().c_str(), 4096 * 20))); + + unique_ptr<MapfileFilesystem> fs = MapfileFilesystem::CreateFromFile( + temp_file_.path(), temp_mapfile_.path()); + ASSERT_NE(nullptr, fs.get()); + + vector<FilesystemInterface::File> files; + EXPECT_TRUE(fs->GetFiles(&files)); + + map<string, FilesystemInterface::File> map_files; + for (const auto& file : files) { + EXPECT_EQ(map_files.end(), map_files.find(file.name)) + << "File " << file.name << " repeated in the list."; + map_files[file.name] = file; + ExpectBlocksInRange(file.extents, fs->GetBlockCount()); + } + + EXPECT_EQ(map_files["/fileA"].extents, + (vector<Extent>{ExtentForRange(1, 1)})); + EXPECT_EQ(map_files["/fileB"].extents, + (vector<Extent>{ExtentForRange(2, 3)})); + EXPECT_EQ( + map_files["/fileC"].extents, + (vector<Extent>{ + ExtentForRange(5, 2), ExtentForRange(9, 1), ExtentForRange(11, 2)})); + EXPECT_EQ(map_files["/file with spaces"].extents, + (vector<Extent>{ExtentForRange(14, 1), ExtentForRange(19, 1)})); + EXPECT_EQ(map_files["/1234"].extents, (vector<Extent>{ExtentForRange(7, 1)})); +} + +TEST_F(MapfileFilesystemTest, BlockNumberTooBigTest) { + test_utils::WriteFileString(temp_mapfile_.path(), "/some/file 1-4\n"); + EXPECT_EQ(0, HANDLE_EINTR(truncate(temp_file_.path().c_str(), 4096 * 3))); + + unique_ptr<MapfileFilesystem> fs = MapfileFilesystem::CreateFromFile( + temp_file_.path(), temp_mapfile_.path()); + ASSERT_NE(nullptr, fs.get()); + + vector<FilesystemInterface::File> files; + EXPECT_FALSE(fs->GetFiles(&files)); +} + +TEST_F(MapfileFilesystemTest, EndBeforeStartTest) { + test_utils::WriteFileString(temp_mapfile_.path(), "/some/file 2-1\n"); + EXPECT_EQ(0, HANDLE_EINTR(truncate(temp_file_.path().c_str(), 4096 * 3))); + + unique_ptr<MapfileFilesystem> fs = MapfileFilesystem::CreateFromFile( + temp_file_.path(), temp_mapfile_.path()); + ASSERT_NE(nullptr, fs.get()); + + vector<FilesystemInterface::File> files; + EXPECT_FALSE(fs->GetFiles(&files)); +} + +} // namespace chromeos_update_engine 
diff --git a/payload_generator/payload_generation_config.cc b/payload_generator/payload_generation_config.cc index 8ef30a0..c7ecca7 100644 --- a/payload_generator/payload_generation_config.cc +++ b/payload_generator/payload_generation_config.cc 
@@ -22,6 +22,7 @@  #include "update_engine/payload_consumer/delta_performer.h"  #include "update_engine/payload_generator/delta_diff_generator.h"  #include "update_engine/payload_generator/ext2_filesystem.h" +#include "update_engine/payload_generator/mapfile_filesystem.h"  #include "update_engine/payload_generator/raw_filesystem.h"    namespace chromeos_update_engine { @@ -58,14 +59,18 @@  fs_interface = Ext2Filesystem::CreateFromFile(path);  }   - if (!fs_interface) { - // Fall back to a RAW filesystem. - TEST_AND_RETURN_FALSE(size % kBlockSize == 0); - fs_interface = RawFilesystem::Create( - "<" + name + "-partition>", - kBlockSize, - size / kBlockSize); + if (!mapfile_path.empty()) { + fs_interface = MapfileFilesystem::CreateFromFile(path, mapfile_path); + if (fs_interface) { + TEST_AND_RETURN_FALSE(fs_interface->GetBlockSize() == kBlockSize); + return true; + }  } + + // Fall back to a RAW filesystem. + TEST_AND_RETURN_FALSE(size % kBlockSize == 0); + fs_interface = RawFilesystem::Create( + "<" + name + "-partition>", kBlockSize, size / kBlockSize);  return true;  }   
diff --git a/payload_generator/payload_generation_config.h b/payload_generator/payload_generation_config.h index 373c7cd..8617d14 100644 --- a/payload_generator/payload_generation_config.h +++ b/payload_generator/payload_generation_config.h 
@@ -66,6 +66,11 @@  // device such as a loop device.  std::string path;   + // The path to the .map file associated with |path| if any. The .map file is + // generated by the Android filesystem generation tools when creating a + // filesystem and describes the blocks used by each file. + std::string mapfile_path; +  // The size of the data in |path|. If rootfs verification is used (verity)  // this value should match the size of the verity device for the rootfs, and  // the size of the whole kernel. This value could be smaller than the 
diff --git a/scripts/brillo_update_payload b/scripts/brillo_update_payload index 1649106..8d51118 100755 --- a/scripts/brillo_update_payload +++ b/scripts/brillo_update_payload 
@@ -187,6 +187,11 @@  declare -A SRC_PARTITIONS  declare -A DST_PARTITIONS   +# Associative arrays for the .map files associated with each src/dst partition +# file in SRC_PARTITIONS and DST_PARTITIONS. +declare -A SRC_PARTITIONS_MAP +declare -A DST_PARTITIONS_MAP +  # A list of temporary files to remove during cleanup.  CLEANUP_FILES=()   @@ -420,6 +425,12 @@  part_file="${temp_raw}"  fi   + # Extract the .map file (if one is available). + part_map_file=$(create_tempfile "${part}.map.XXXXXX") + CLEANUP_FILES+=("${part_map_file}") + unzip -p "${image}" "IMAGES/${part}.map" >"${part_map_file}" || \ + part_map_file="" +  # delta_generator only supports images multiple of 4 KiB. For target images  # we pad the data with zeros if needed, but for source images we truncate  # down the data since the last block of the old image could be padded on @@ -437,6 +448,7 @@  fi    eval "${partitions_array}[\"${part}\"]=\"${part_file}\"" + eval "${partitions_array}_MAP[\"${part}\"]=\"${part_map_file}\""  echo "Extracted ${partitions_array}[${part}]: ${filesize} bytes"  done  } @@ -467,27 +479,34 @@  GENERATOR_ARGS=( -out_file="${FLAGS_payload}" )    local part old_partitions="" new_partitions="" partition_names="" + local old_mapfiles="" new_mapfiles=""  for part in "${!DST_PARTITIONS[@]}"; do  if [[ -n "${partition_names}" ]]; then  partition_names+=":"  new_partitions+=":"  old_partitions+=":" + new_mapfiles+=":" + old_mapfiles+=":"  fi  partition_names+="${part}"  new_partitions+="${DST_PARTITIONS[${part}]}"  old_partitions+="${SRC_PARTITIONS[${part}]:-}" + new_mapfiles+="${DST_PARTITIONS_MAP[${part}]:-}" + old_mapfiles+="${SRC_PARTITIONS_MAP[${part}]:-}"  done    # Target image args:  GENERATOR_ARGS+=(  -partition_names="${partition_names}"  -new_partitions="${new_partitions}" + -new_mapfiles="${new_mapfiles}"  )    if [[ "${payload_type}" == "delta" ]]; then  # Source image args:  GENERATOR_ARGS+=(  -old_partitions="${old_partitions}" + -old_mapfiles="${old_mapfiles}"  )  if [[ -n "${FORCE_MINOR_VERSION}" ]]; then  GENERATOR_ARGS+=( --minor_version="${FORCE_MINOR_VERSION}" ) 
diff --git a/update_engine.gyp b/update_engine.gyp index 1bd83b1..43d2a0d 100644 --- a/update_engine.gyp +++ b/update_engine.gyp 
@@ -391,6 +391,7 @@  'payload_generator/graph_types.cc',  'payload_generator/graph_utils.cc',  'payload_generator/inplace_generator.cc', + 'payload_generator/mapfile_filesystem.cc',  'payload_generator/payload_file.cc',  'payload_generator/payload_generation_config.cc',  'payload_generator/payload_signer.cc', @@ -524,6 +525,7 @@  'payload_generator/full_update_generator_unittest.cc',  'payload_generator/graph_utils_unittest.cc',  'payload_generator/inplace_generator_unittest.cc', + 'payload_generator/mapfile_filesystem_unittest.cc',  'payload_generator/payload_file_unittest.cc',  'payload_generator/payload_generation_config_unittest.cc',  'payload_generator/payload_signer_unittest.cc',